package cn.fetosoft.woodpecker.core.jmeter.collector;

import cn.fetosoft.woodpecker.core.data.entity.Report;
import cn.fetosoft.woodpecker.core.enums.ElementCategory;
import cn.fetosoft.woodpecker.core.jmeter.CollectorCallback;
import cn.fetosoft.woodpecker.core.util.Constant;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.reporters.Summariser;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.SamplingStatCalculator;

import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;

/**
 * 聚合报告收集器
 * @author guobingbing
 * @version 1.0
 * @wechat t_gbinb
 * @create 2021/6/30 15:40
 */
public class AggregateReportCollector extends ResultCollector {

	private static final String TOTAL_ROW_LABEL = JMeterUtils.getResString("aggregate_report_total_label");
	private final transient Object lock = new Object();
	private final Map<String, SamplingStatCalculator> tableRows = new ConcurrentHashMap<>();
	private final Deque<CalculatorResult> deque = new ConcurrentLinkedDeque<>();
	private CollectorCallback<Report> callback;

	public AggregateReportCollector(Summariser summer, CollectorCallback<Report> callback){
		super(summer);
		this.callback = callback;
		tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(TOTAL_ROW_LABEL));
	}

	@Override
	protected void sendToVisualizer(SampleResult res) {
		super.sendToVisualizer(res);
		Sampler sampler = JMeterContextService.getContext().getCurrentSampler();
		SamplingStatCalculator row = tableRows.computeIfAbsent(
				res.getSampleLabel(false),
				label -> {
					SamplingStatCalculator newRow = new SamplingStatCalculator(label);
					CalculatorResult calculator = new CalculatorResult();
					calculator.setCalculator(newRow);
					calculator.setThreadId(sampler.getThreadId());
					calculator.setPlanId(sampler.getPropertyAsString(Constant.PLAN_ID));
					calculator.setElementId(sampler.getPropertyAsString(Constant.ELEMENT_ID));
					calculator.setTestId(sampler.getPropertyAsString(Constant.TEST_ID));
					calculator.setUserId(sampler.getPropertyAsString(Constant.USER_ID));
					calculator.setCategory(sampler.getPropertyAsString(Constant.CATEGORY));
					calculator.setParentId(sampler.getPropertyAsString(Constant.PARENT_ID));
					deque.add(calculator);
					return newRow;
				});
		synchronized (row) {
			row.addSample(res);
		}
		SamplingStatCalculator tot = tableRows.get(TOTAL_ROW_LABEL);
		synchronized(lock) {
			tot.addSample(res);
		}
	}

	@Override
	public void testEnded() {
		super.testEnded();
		synchronized (lock) {
			Report report = null;
			while (!deque.isEmpty()) {
				CalculatorResult result = deque.pop();
				if(callback!=null){
					report = new Report();
					report.setThreadId(result.getThreadId());
					report.setElementId(result.getElementId());
					report.setPlanId(result.getPlanId());
					report.setTestId(result.getTestId());
					report.setUserId(result.getUserId());
					report.setParentId(result.getParentId());
					report.setCategory(result.getCategory());

					SamplingStatCalculator calculator = result.getCalculator();
					this.copy(report, calculator);
					callback.call(report);
				}
			}
			if(report!=null){
				SamplingStatCalculator calculator = tableRows.get(TOTAL_ROW_LABEL);
				Report totalReport = new Report();
				totalReport.setPlanId(report.getPlanId());
				totalReport.setTestId(report.getTestId());
				totalReport.setUserId(report.getUserId());
				totalReport.setParentId(ElementCategory.AGGREGATE_REPORT.getName());
				totalReport.setCategory("total");
				this.copy(totalReport, calculator);
				callback.call(totalReport);
			}
		}
		this.clearData();
	}

	@Override
	public void clearData() {
		synchronized (lock) {
			tableRows.clear();
			deque.clear();
			tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(TOTAL_ROW_LABEL));
		}
	}

	/**
	 * copy data
	 * @param report
	 * @param calculator
	 */
	private void copy(Report report, SamplingStatCalculator calculator){
		report.setName(calculator.getLabel());
		report.setSamples(calculator.getCount());
		report.setAverage(calculator.getCurrentSample().getAverage());
		report.setMedian(calculator.getMedian());
		report.setPercent90(calculator.getPercentPoint((float) 0.90));
		report.setPercent95(calculator.getPercentPoint((float) 0.95));
		report.setPercent99(calculator.getPercentPoint((float) 0.99));
		report.setMin(calculator.getMin());
		report.setMax(calculator.getMax());
		report.setErrorRate(calculator.getErrorPercentage());
		report.setThroughput(calculator.getMaxThroughput());
		report.setSendBytes(calculator.getSentKBPerSecond());
		report.setReceiveBytes(calculator.getKBPerSecond());
		report.setCreateTime(calculator.getCurrentSample().getStartTime());
		report.setModifyTime(calculator.getCurrentSample().getEndTime());
	}

	static class CalculatorResult{

		private String threadId;
		private String elementId;
		private String planId;
		private String testId;
		private String userId;
		private String parentId;
		private String category;
		SamplingStatCalculator calculator;

		public String getThreadId() {
			return threadId;
		}

		public void setThreadId(String threadId) {
			this.threadId = threadId;
		}

		public String getElementId() {
			return elementId;
		}

		public void setElementId(String elementId) {
			this.elementId = elementId;
		}

		public String getPlanId() {
			return planId;
		}

		public void setPlanId(String planId) {
			this.planId = planId;
		}

		public String getTestId() {
			return testId;
		}

		public void setTestId(String testId) {
			this.testId = testId;
		}

		public String getUserId() {
			return userId;
		}

		public void setUserId(String userId) {
			this.userId = userId;
		}

		public String getParentId() {
			return parentId;
		}

		public void setParentId(String parentId) {
			this.parentId = parentId;
		}

		public String getCategory() {
			return category;
		}

		public void setCategory(String category) {
			this.category = category;
		}

		public SamplingStatCalculator getCalculator() {
			return calculator;
		}

		public void setCalculator(SamplingStatCalculator calculator) {
			this.calculator = calculator;
		}
	}
}
